﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Common.Encrypt
{
    /// <summary>
    /// 数字54进制
    /// </summary>
    public class NumberBase54
    {
        //自定义进制所用的编码，大小写和数字(初始62位)，但去掉7位相似：O/o/0,I/i/1/l，去掉一个补位：A;最终只留(26+26+10)-(7+1)=54位
        private static readonly char[] BASE = new char[]
        {'a','H','b', 'V','c', 'E', '8', 'S', '2', 'D','d', 'e','f','Z','g', 'X','h', '9','z','y', 'C','x', '7', 'P','p','5', 'K',
         '3', 't','M','q', 'J','r','s', 'U', 'F', 'R', '4', 'u','W', 'n','Y', 'L', 'm','T', 'N','w', '6','v', 'B', 'G', 'Q','j','k',
        };

        //A补位字符，不能与自定义重复
        private static readonly char SUFFIX_CHAR = 'A';

        //进制长度
        private static readonly int BIN_LEN = BASE.Length;

        //生成邀请码最小长度
        private static readonly int CODE_LEN = 5;

        /// <summary>
        /// 转换为Base54编码
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static string Encode(long id)
        {
            char[] buf = new char[BIN_LEN];
            int charPos = BIN_LEN;

            //当id除以数组长度结果大于0，则进行取模操作，并以取模的值作为数组的坐标获得对应的字符
            while (id / BIN_LEN > 0)
            {
                int index = (int)(id % BIN_LEN);
                buf[--charPos] = BASE[index];
                id /= BIN_LEN;
            }

            buf[--charPos] = BASE[(int)(id % BIN_LEN)];
            // 将字符数组转化为字符串
            string result = new string(buf, charPos, BIN_LEN - charPos);

            // 长度不足指定长度则随机补全
            int len = result.Length;
            if (len < CODE_LEN)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append(SUFFIX_CHAR);
                Random random = new Random();
                // 去除SUFFIX_CHAR本身占位之后需要补齐的位数
                for (int i = 0; i < CODE_LEN - len - 1; i++)
                {
                    sb.Append(BASE[random.Next(BIN_LEN)]);
                }

                result += sb.ToString();
            }
            //加一位验证码
            result = FillCheckCode(result);
            return result;
        }

        /// <summary>
        /// Base54编码转换数字
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        public static long Decode(string code)
        {
            //验证校验码
            var verifyResult = VerifyCheckCode(code);
            if (!verifyResult.Item1)
            {
                throw new Exception($"无效的编码:{code}");
            }
            //逆向解析
            code = verifyResult.Item2;
            char[] charArray = code.ToCharArray();
            long result = 0L;
            for (int i = 0; i < charArray.Length; i++)
            {
                int index = 0;
                for (int j = 0; j < BIN_LEN; j++)
                {
                    if (charArray[i] == BASE[j])
                    {
                        index = j;
                        break;
                    }
                }

                if (charArray[i] == SUFFIX_CHAR)
                {
                    break;
                }

                if (i > 0)
                {
                    result = result * BIN_LEN + index;
                }
                else
                {
                    result = index;
                }
            }

            return result;
        }

        /// <summary>
        /// 验证有效性
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        public static bool Verify(string code)
        {
            var result = VerifyCheckCode(code);
            return result.Item1;
        }

        /// <summary>
        /// 填补一位校验码
        /// </summary>
        /// <param name="base54"></param>
        /// <returns></returns>
        private static string FillCheckCode(string base54)
        {
            int sumIndex = 0;
            string baseValue = string.Join("", BASE);
            foreach (var item in base54)
            {
                int i = baseValue.IndexOf(item);
                sumIndex += i;
            }
            char code = BASE[sumIndex % BASE.Length];
            return base54 + code;
        }

        /// <summary>
        /// 验证有效性，并返还原始编码
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        private static (bool, string) VerifyCheckCode(string code)
        {
            if (code?.Length != (CODE_LEN + 1))
            {
                return (false, string.Empty);
            }
            var lastChar = code[code.Length - 1];

            var base54 = code.Substring(0, code.Length - 1);
            int sumIndex = 0;
            string baseValue = string.Join("", BASE);
            foreach (var item in base54)
            {
                int i = baseValue.IndexOf(item);
                sumIndex += i;
            }
            char checkCode = BASE[sumIndex % BASE.Length];
            if (lastChar != checkCode)
            {
                return (false, string.Empty);
            }
            return (true, base54);
        }
    }
}
